import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <p>Title: HttpdConf.java</p>
 *
 * <p>Description: This class will configure your server to the specifications
 * found within the httpd.conf file. </p>
 *
 * <p>Copyright: Copyright (c) 2011</p>
 *
 * <p>Company: </p>
 * @author David Beaver and Aishwarya Iyer
 * @version 1.0
 */
public class HttpdConf {
    private BufferedReader configFile, MIMEFile;
    private Integer listenPort, maxThread;
    private String serverRoot, documentRoot, serverAdmin;
    private HashMap<String, String> optionMap;
    private HashMap<String, String> aliasMap;
    private HashMap<String, String> MIMEMap;
    private HashMap<String, List<String>> authMap;
    private BufferedWriter logFile;

    /**
    * Default constructor to reset your variables and data structures.
    */
    public HttpdConf(){
        try {
            this.logFile = new BufferedWriter(new FileWriter("C:\\Dash\\logfile.txt",true));
        } catch (IOException ex) {
            Logger.getLogger(HttpdConf.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.serverRoot = "";
        this.serverAdmin = "";
        this.documentRoot = "";
        this.maxThread = new Integer(0);
        this.listenPort = new Integer(0);
        this.configFile = null;
        this.MIMEFile = null;
        this.optionMap = new HashMap<String, String>();
        this.MIMEMap = new HashMap<String, String>();
        this.aliasMap = new HashMap<String, String>();
        this.authMap = new HashMap<String, List<String>>();
    }

    
    //other get methods to add
    //get protocol
    //get name

    //-------------------------
    //returns the MIME map
    public HashMap<String,String> getMIME(){
      return this.MIMEMap;
    }

    public Integer getPort(){
        return this.listenPort;
    }
    public String getName(){
        return this.optionMap.get("ServerName");
    }

    public String getProtocol(){
        return "HTTP/1.1";
    }

    public void writeLogFile(String log){
        try {
            this.logFile.write(log);
            this.logFile.flush();
        } catch (IOException ex) {
            Logger.getLogger(HttpdConf.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
    * Reads in a httpd.conf file, parses it and saves the data stored within that
    * file. This allows for proper configuration of your server since the
    * information stored in your configuration file should allow for your server
    * to function.
    *
    * @param path path to your httpd.conf file
    */
    public void readHttpd(String path){
        String nextLine = "";
        String option = "";
        String value = "";
        String realPath = "";
        String aliasPath = "";
        try {
            this.configFile = new BufferedReader(new FileReader(path)); //open the configutation file
            while(configFile.ready()){ //if the file was opened successfully
                nextLine = configFile.readLine().replace('"', ' ').trim(); //read the next line and remove quotations for further parsing
                if(nextLine.contains("<Directory ")){
                    StringTokenizer authParser = new StringTokenizer(nextLine);
                    String directory = authParser.nextToken();
                    directory = authParser.nextToken(">");
                    ArrayList<String> authentication = new ArrayList<String>();
                    do{
                        nextLine = configFile.readLine().replace('"', ' ').replace('>', ' ').trim();
                        authParser = new StringTokenizer(nextLine);
                        if(nextLine.contains("AuthUserFile")){
                            String authFile = authParser.nextToken();
                            authFile = authParser.nextToken();
                            authFile = authFile.replace('"', ' ');
                            authentication.add(authFile.trim());
                        }else if(nextLine.contains("Require")){
                           authParser.nextToken();
                           authParser.nextToken();
                           while(authParser.hasMoreTokens()){
                               authentication.add(authParser.nextToken().replace(',', ' ').trim());
                           }
                        }
                    }while(!nextLine.contains("</Directory"));
                    this.authMap.put(directory.trim(),authentication);
                }
                else if(nextLine.charAt(0) != '#' && !nextLine.isEmpty())
                { //if the new line isn't a comment or empty
                    StringTokenizer configLoader = new StringTokenizer(nextLine); //parse the line
                    if(configLoader.hasMoreTokens()){ //if options were found
                        option = configLoader.nextToken(); //get the next option
                        if(option.equals("Alias") || option.equals("ScriptAlias")){ //if it's an alias
                            aliasPath = configLoader.nextToken().trim();
                            if(aliasPath.equals("/")){
                                realPath = configLoader.nextToken("\n").trim(); //get the real path
                                this.aliasMap.put(aliasPath, realPath); //map the alias path to the real path
                            }else{
                                //aliasPath = configLoader.nextToken().trim(); //get the alias path
                                realPath = configLoader.nextToken("\n").trim(); //get the real path
                                this.aliasMap.put(aliasPath, realPath); //map the alias path to the real path
                            }
                        }else{ //its an option
                            value = configLoader.nextToken("\n"); //get the option's value
                            this.optionMap.put(option, value);  //map the option to it's value
                        }
                    }
                }
            }
        } catch (FileNotFoundException ex) {
            this.writeLogFile("**Error**: Could not find the configuration file\n");
            System.out.println("Could not find configuration file");
        } catch (IOException ex){
            this.writeLogFile("**Error**: Could not read the configuration file\n");
            System.out.println("Error reading the configuration file");
        }
        //retrieve values from the configuration and assign values
        this.listenPort = new Integer(this.optionMap.get("Listen").trim()); //assign the port
        this.serverRoot = this.optionMap.get("ServerRoot"); //assign the server root
        this.serverAdmin = this.optionMap.get("ServerAdmin"); //assign the server admin
        this.documentRoot = this.optionMap.get("DocumentRoot"); //assign the document root
    }

    public boolean needsAuthentication(String file){
        String folder = this.getFolder(file);
        if(this.authMap.containsKey(folder)){
            return true;
        }
        return false;
    }

    public boolean verifyAuthentication(String file, byte[] decodedAuth) {
        String auth = new String(decodedAuth);
        StringTokenizer authParser = new StringTokenizer(auth,":");
        String username = authParser.nextToken();
        String passwd = authParser.nextToken();
        String folder = this.getFolder(file);
        List<String> credentials = this.authMap.get(folder);
        if(credentials.contains(username)){ //if this user is required
            File passwordFile = new File(credentials.get(0));
            try {
                FileReader fileContents = new FileReader(passwordFile);
                BufferedReader fileReader = new BufferedReader(fileContents);
                while(fileReader.ready()){
                    String line = fileReader.readLine();
                    if(auth.equals(line)){
                        return true;
                    }
                }
                return false;
            } catch (FileNotFoundException ex) {
                Logger.getLogger(HttpdConf.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException iex){}
        }
        return false;
    }
    /**
    * Function to convert aliases set within your httpd.conf file to their
    * absolute path. This allows for aliases to be found on your server and
    * returned back to the client.
    * HINT: You may find it helpful to create a private class to store your
    * aliases.
    *
    * @param fakeName String which contains the alias of the file or directory
    * @return String value which contains the absolute path to the file or
    *   directory as determined within the httpd.conf file
    */
    // /   /docs/bin.html
    //******************** this function requires fixing ************************
    //the paths dont resolve correctly
    public String solveAlias(String alias){
        String folder = "";
        String newPath = "";
        //need to check if there is any specified path
        if(alias.length() == 1 && alias.charAt(0) == '/'){ //accessing the index file
            newPath = this.aliasMap.get("/") + "/index.html";
            return newPath;
        }
        //accessing something in the root file
        StringTokenizer URIParser = new StringTokenizer(alias,"/");

        if(URIParser.countTokens() == 1){ //accessing the root folder
            newPath = this.aliasMap.get("/") + "/" + URIParser.nextToken();
            return newPath;
        }
        while(URIParser.hasMoreTokens())
        {
          folder = URIParser.nextToken();
          if(this.isScript("/"+folder+"/")){
              newPath += this.aliasMap.get("/"+folder+"/");
              if(URIParser.countTokens() >= 1){
                 newPath += "/";
              }
          }else{
              newPath += folder + "/";
          }
        }
        return newPath;
    }
    public String getFolder(String alias){
        String folder = "";
        String newPath = "";
        //need to check if there is any specified path
        if(alias.length() == 1 && alias.charAt(0) == '/'){ //accessing the index file
            newPath = this.aliasMap.get("/");
            return newPath;
        }
        //accessing something in the root file
        StringTokenizer URIParser = new StringTokenizer(alias,"/");
        while(URIParser.hasMoreTokens())
        {
          folder = URIParser.nextToken();
          if(this.isScript("/"+folder+"/")){
              newPath += this.aliasMap.get("/"+folder+"/");
          }
        }
        return newPath;
    }
  
    public String getMediaType(String mediaExtension){
        return this.MIMEMap.get(mediaExtension);
    }
    /**
    * Used to read the mime.types file and save all the information from that file
    * into a data structure that can be used to validate file types when
    * generating response messages.
    *
    * @param path String value of path to mime.types file
    */
    public void readMIME (String path) {
        String mediaType = "";
        String mediaExtension = "";
        String nextLine = "";
        try{
            MIMEFile = new BufferedReader(new FileReader(path)); //open MIME file
            while(MIMEFile.ready()){ //if the MIME file was opeend successfully openened
                nextLine = MIMEFile.readLine(); //read the next line
                if((nextLine.charAt(0) != '#') && !nextLine.isEmpty()){ //if the new line isnt blank and a comment
                    StringTokenizer MIMELoader = new StringTokenizer(nextLine); //read the next line into the parser
                    if(MIMELoader.hasMoreTokens()){ //if the parser found something
                        mediaType = MIMELoader.nextToken(); //save the media type
                        while(MIMELoader.hasMoreTokens()){ //if extensions are found
                            mediaExtension = MIMELoader.nextToken(); //save the extension
                            this.MIMEMap.put(mediaExtension, mediaType); //map the extension to the media type
                        }
                    }
                }

            }
        } catch(FileNotFoundException ex){
            this.writeLogFile("**Error**: Could not find the MIME file\n");
            System.out.println("Could not find MIME configuration file");
        } catch (IOException ex){
            this.writeLogFile("**Error**: Could not read the MIME file\n");
            System.out.println("Error reading the MIME configuration file");
        }
    }

    /**
    * Helper function to determine whether the name of a file or directory is an
    * alias for another file or directory as noted in the httpd.conf file.
    *
    * @param name String value of the alias we want to check to determine
    *   whether it is or is not an alias for another file or directory
    * @return true if it is an alias, false otherwise
    */
    public boolean isScript(String name){
        return this.aliasMap.containsKey(name);
    }

    private String getRealPath(String alias){
        return this.aliasMap.get(alias);
    }


    /**
    * Helper function to see if you've parsed your httpd.conf file properly. Used
    * for debugging purposes.
    */
    public void testPrint(){
        System.out.println("ServerRoot: " + serverRoot.trim());
        System.out.println("DocumentRoot: " + documentRoot.trim());
        System.out.println("ListenPort: " + listenPort);
        System.out.println("\nAliases Mapped:");
        Set<String> settings = this.aliasMap.keySet();
        for(String setting : settings){
            System.out.println(setting + "--->" + this.aliasMap.get(setting));
        }
        //System.out.println(this.aliasMap.toString());
        System.out.println("\nMIME Options:");
        settings = this.MIMEMap.keySet();
        for(String setting : settings){
            System.out.println(setting + "--->" + this.MIMEMap.get(setting));
        }
        //System.out.println(this.MIMEMap.toString()+"\n");
        //System.out.println("LogFile: "+logFile);
        //System.out.println("AccessFileName: " + accessFileName);
        //System.out.println("ScriptAlias: "+scriptAlias+" "+solveAlias(scriptAlias));
    }
}
